package com.cloudweb.oa.security;

import cn.js.fan.cache.jcs.RMCache;
import cn.js.fan.util.ErrMsgException;
import cn.js.fan.util.StrUtil;
import cn.js.fan.web.Global;
import cn.js.fan.web.SkinUtil;
import cn.ljserver.tool.weboffice.v3.config.WebOfficeProperties;
import com.alibaba.fastjson.JSONObject;
import com.cloudweb.oa.api.IFormulaUtil;
import com.cloudweb.oa.api.ILicenseUtil;
import com.cloudweb.oa.api.ILoginUtil;
import com.cloudweb.oa.config.JwtProperties;
import com.cloudweb.oa.utils.*;
import com.cloudwebsoft.framework.util.LogUtil;
import com.redmoon.oa.sms.SMSFactory;
import com.redmoon.oa.ui.PortalDb;
import com.redmoon.oa.visual.FormulaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Vector;

@Component
public class LoginUtil implements ILoginUtil {
    private static final long DELAY_TIME = 60000; // 延时时间为60秒
    private static final int MAX_FAIL_COUNT = 3;
    private static final long FAIL_TIME_SPAN = 20000; // 在半分钟内的最大出错次数为3
    private static final String GROUP_NAME = "CWS_LOGIN";

    private static final String LOGIN_FAIL_COUNT = "_login_fail_count";
    private static final String LOGIN_FAIL_FIRST = "_login_fail_first";
    private static final String LOGIN_FAIL_LAST = "_login_fail_last";
    private static final String LOGIN_TOKEN = "_login_token";

    private static final String LOGIN_TOKEN_MOBILE = "_login_token_mobile";

    @Autowired
    JwtUtil jwtUtil;

    @Autowired
    JwtProperties jwtProperties;

    @Autowired
    SysUtil sysUtil;

    @Autowired
    SysProperties sysProperties;

    @Autowired
    I18nUtil i18nUtil;

    @Autowired
    ILicenseUtil licenseUtil;

    @Autowired
    WebOfficeProperties webOfficeProperties;

    @Autowired
    IFormulaUtil formulaUtil;

    public LoginUtil() {
    }

    /**
     * 初始化登录失败次数为0，在登录界面中使用,用于防暴力破解
     * @param userName
     */
    public void initLogin(String userName) {
        if (!RMCache.getInstance().getCanCache()) {
            return;
        }
        Integer c = (Integer)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME);
        if (c == null) {
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME, 0);
        }
    }

    @Override
    public boolean canLogin(String userName) throws
            ErrMsgException {
        if (!RMCache.getInstance().getCanCache()) {
            return true;
        }
        Integer count = (Integer)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME);
        if (count == null) {
            count = 0;
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME, count);
        }

        if (count < MAX_FAIL_COUNT) {
            return true;
        }
        Long first = 0L;
        Long last = 0L;
        try {
            first = (Long)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_FIRST, GROUP_NAME);
            last = (Long)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_LAST, GROUP_NAME);
        } catch (NumberFormatException e) {
            throw new ErrMsgException("时间非法！");
        }
        long timespan = last - first;
        // 出错大于maxfailcount时，如果时间间隔小于预定值，则怀疑被攻击，采取措施
        if (timespan <= FAIL_TIME_SPAN) {
            long tspan = System.currentTimeMillis() - last;
            tspan = (DELAY_TIME - tspan) / 1000;
            if (tspan > 0) {
                // throw new ErrMsgException("对不起，您已在" + FAIL_TIME_SPAN / 1000 +
                //                          "秒内登录出错超过" + MAX_FAIL_COUNT + "次，您被延时" +
                //                          DELAY_TIME / 1000 + "秒登录，您还需" + tspan +
                //                          "秒才可以登录！");
                String str = i18nUtil.get("err_login_can_not");
                str = str.replaceFirst("\\$s", "" + FAIL_TIME_SPAN/1000);
                str = str.replaceFirst("\\$c", "" + MAX_FAIL_COUNT);
                str = str.replaceFirst("\\$d", "" + DELAY_TIME/1000);
                str = str.replaceFirst("\\$t", "" + tspan);
                throw new ErrMsgException(str);
            }
        } else {
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME, 0);
        }
        return true;
    }

    /**
     * 根据登录是否成功修改缓存中的相应的变量
     */
    @Override
    public void afterLoginFailure(String userName) throws ErrMsgException {
        if (!RMCache.getInstance().getCanCache()) {
            return;
        }

        Integer c = (Integer)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME);
        if (c == null) {
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME, 0);
        }

        if (c == null) {
            // throw new ErrMsgException("After:" + i18nUtil.get("err_login_invalid")); //非法登录，因为在登录界面时未写入session zjpages_loginfailcount的值
            c = 0;
        }
        c++;
        RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_COUNT, GROUP_NAME, c);

        long t = System.currentTimeMillis();
        // 置登录失败第一次的时间和最后次的时间
        if (c == 1) {
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_FIRST, GROUP_NAME, t);
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_LAST, GROUP_NAME, t);
        } else {
            RMCache.getInstance().putInGroup(userName + LOGIN_FAIL_LAST, GROUP_NAME, t);
        }
        Long timespan = 0L;
        Long first = 0L, last = 0L;
        if (c == 1) {
            // throw new ErrMsgException("您已失败了" + count + "次！请注意：如果" +
            //                          FAIL_TIME_SPAN / 1000 + "秒内大于" +
            //                          MAX_FAIL_COUNT + "次您将被延时" +
            //                          DELAY_TIME / 1000 + "秒登录！");
            String str = i18nUtil.get("err_login_fail_one");
            str = str.replaceFirst("\\$c", "" + c);
            str = str.replaceFirst("\\$s", "" + FAIL_TIME_SPAN / 1000);
            str = str.replaceFirst("\\$m", "" + MAX_FAIL_COUNT);
            str = str.replaceFirst("\\$d", "" + DELAY_TIME / 1000);
            throw new ErrMsgException(str);
        } else {
            last = t;
        }
        try {
            first = (Long)RMCache.getInstance().getFromGroup(userName + LOGIN_FAIL_FIRST, GROUP_NAME);
        } catch (NumberFormatException e) {
            throw new ErrMsgException("时间格式错！");
        }
        timespan = (last - first) / 1000;
        // throw new ErrMsgException("您已在" + timespan + "秒中失败了" + count +
        //                          "次！请注意：如果" + FAIL_TIME_SPAN / 1000 + "秒内大于" +
        //                          MAX_FAIL_COUNT + "次您将被延时" + DELAY_TIME / 1000 +
        //                          "秒登录！");
        String str = i18nUtil.get("err_login_fail");
        str = str.replaceFirst("\\$t", "" + timespan);
        str = str.replaceFirst("\\$c", "" + c);
        str = str.replaceFirst("\\$f", "" + FAIL_TIME_SPAN/1000);
        str = str.replaceFirst("\\$m", "" + MAX_FAIL_COUNT);
        str = str.replaceFirst("\\$s", "" + DELAY_TIME/1000);
        throw new ErrMsgException(str);
    }

    @Override
    public void setLoginInfo(HttpServletResponse response, String userName, JSONObject json) {
        String authToken = jwtUtil.generate(userName);
        response.setHeader(jwtProperties.getHeader(), authToken);

        json.put(jwtProperties.getHeader(), authToken);
        json.put("serverInfo", generateServerInfo(userName));

        // 防止多重登录
        putTokenInCache(userName, authToken);
    }

    @Override
    public void putTokenInCache(String userName, String token) {
        // 置于缓存中，用于防止多重登录，缓存时长与jwt token的一致
        if (StringUtils.hasText(token) && token.startsWith(jwtProperties.getStartWith())) {
            token = token.substring(jwtProperties.getStartWith().length());
        }

        RMCache.getInstance().putInGroup(userName + LOGIN_TOKEN, GROUP_NAME, token, jwtProperties.getValidateSecond().intValue());
    }

    @Override
    public String getTokeByCache(String userName) {
        return (String)RMCache.getInstance().getFromGroup(userName + LOGIN_TOKEN, GROUP_NAME);
    }

    @Override
    public void putMobileTokenInCache(String userName, String token) {
        // 置于缓存中，用于防止多重登录，缓存时长与jwt token的一致
        if (StringUtils.hasText(token) && token.startsWith(jwtProperties.getStartWith())) {
            token = token.substring(jwtProperties.getStartWith().length());
        }

        RMCache.getInstance().putInGroup(userName + LOGIN_TOKEN_MOBILE, GROUP_NAME, token, jwtProperties.getValidateSecond().intValue());
    }

    @Override
    public String getMobileTokeByCache(String userName) {
        return (String)RMCache.getInstance().getFromGroup(userName + LOGIN_TOKEN_MOBILE, GROUP_NAME);
    }

    public JSONObject generateServerInfo(String userName) {
        com.redmoon.oa.Config cfg = com.redmoon.oa.Config.getInstance();
        JSONObject jsonObject = sysUtil.getServerInfo();

        // 用户的首页门户
        PortalDb pd = new PortalDb();
        pd = (PortalDb)pd.getQObjectDb(PortalDb.DESKTOP_DEFAULT_ID);

        boolean canSeeHome = pd.canUserSee(userName) && pd.getInt("status") == 1;
        if (canSeeHome) {
            jsonObject.put("portalId", PortalDb.DESKTOP_DEFAULT_ID);
        } else {
            int portalId = PortalDb.DESKTOP_DEFAULT_ID;
            Vector<PortalDb> v = pd.listByKind(PortalDb.KIND_DESKTOP);
            for (PortalDb portalDb : v) {
                if (portalDb.canUserSee(userName)) {
                    portalId = portalDb.getInt("id");
                    break;
                }
            }
            jsonObject.put("portalId", portalId);
        }

        // 菜单项所属的应用
        jsonObject.put("isMenuGroupByApplication", cfg.getBooleanProperty("isMenuGroupByApplication"));
        jsonObject.put("isExportExcelAsync", sysProperties.isExportExcelAsync());
        jsonObject.put("isObjStoreEnabled", licenseUtil.isEnabled(LicenseConstUtil.OBS) && sysProperties.isObjStoreEnabled() && sysProperties.isObjStoreFront());
        // 是否启用悬浮进度栏
        jsonObject.put("isUploadPanel", sysProperties.isUploadPanel());
        // 是否启用悬浮按钮，如果为否，则使用顶栏的按钮
        jsonObject.put("isUploadPanelBtnSuspension", sysProperties.isUploadPanelBtnSuspension());

        String version = StrUtil.getNullStr(cfg.get("version"));
        com.redmoon.oa.SpConfig spCfg = new com.redmoon.oa.SpConfig();
        String spVersion = StrUtil.getNullStr(spCfg.get("version"));
        // 系统版本
        jsonObject.put("version", version);
        // 补丁版本
        jsonObject.put("spVersion", spVersion);
        // OA版本：标准版、专业版、企业版等
        jsonObject.put("versionType", licenseUtil.getVersionType());
        jsonObject.put("isCloud", cfg.getBooleanProperty("isCloud"));
        int refreshOnlineUserNotify = cfg.getInt("refreshOnlineUserNotify"); // 用户每隔多长时间，向服务器通报一次在线（秒）
        jsonObject.put("refreshOnlineUserNotify", refreshOnlineUserNotify);
        String refreshMessage = cfg.get("refresh_message");
        jsonObject.put("refreshMessage", refreshMessage);
        boolean isWatermark = cfg.getBooleanProperty("isWatermark");
        jsonObject.put("isWatermark", isWatermark);
        // 系统是否处于调试状态
        jsonObject.put("isDebug", Global.getInstance().isDebug());
        jsonObject.put("isUseSms", SMSFactory.isUseSMS());

        int formEditorType = cfg.getInt("formEditorType");
        // 如果不允许使用业务表单，则强制为0，即只能使用专业表单
        if (!licenseUtil.isEnabled(LicenseConstUtil.FORM_DESIGNER)) {
            formEditorType = 0;
        }
        jsonObject.put("formEditorType", formEditorType);
        jsonObject.put("isUseClient", cfg.getBoolean("isUseClient"));

        jsonObject.put("webOfficeAppId", webOfficeProperties.getConvert().getAppid());

        jsonObject.put("isUseBreakpointResume", cfg.getBoolean("isUseBreakpointResume"));

        // 0 可编辑 1输入框中只读但可设计 2输入框中只读且不可设计
        jsonObject.put("isScriptEditable", cfg.getInt("isScriptEditable"));

        jsonObject.put("isExprEnabled", licenseUtil.isEnabled(LicenseConstUtil.EXPR));
        jsonObject.put("isWriteBackEnabled", licenseUtil.isEnabled(LicenseConstUtil.WRITE_BACK));
        jsonObject.put("isViewRuleEnabled", licenseUtil.isEnabled(LicenseConstUtil.VIEW_RULE));
        jsonObject.put("isCheckRuleEnabled", licenseUtil.isEnabled(LicenseConstUtil.CHECK_RULE));
        jsonObject.put("isRelateQueryEnabled", licenseUtil.isEnabled(LicenseConstUtil.RELATE_QUERY));
        jsonObject.put("isMultiFormEnabled", licenseUtil.isEnabled(LicenseConstUtil.MULTI_FORM));
        jsonObject.put("isFlowRollbackEnabled", licenseUtil.isEnabled(LicenseConstUtil.FLOW_ROLLBACK));

        jsonObject.put("objStoreType", sysProperties.getObjStoreType());

        jsonObject.put("autoRefresh", cfg.getInt("autoRefresh"));
        jsonObject.put("tableDensity", cfg.get("tableDensity"));
        jsonObject.put("tableBordered", cfg.getBoolean("tableBordered"));

        jsonObject.put("isFormLogEnabled", licenseUtil.isEnabled(LicenseConstUtil.FORM_LOG));
        jsonObject.put("isConnectorEnabled", licenseUtil.isEnabled(LicenseConstUtil.CONNECTOR));

        jsonObject.put("isInredEnabled", licenseUtil.isEnabled(LicenseConstUtil.INRED));

        return jsonObject;
    }

    /**
     * 登录后事件
     * @param userName 用户名
     */
    @Override
    public void afterLoginSuccess(String userName) {
        String formula = "#loginOnSuccess(\"" + userName + "\")";
        try {
            FormulaResult fr = formulaUtil.render(formula);
            fr.getValue();
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).warn(e.getMessage());
        }
    }
}
